home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-04-12 | 64.0 KB | 1,538 lines | [TEXT/R*ch] |
- ****
- **** Please Note
- ****
- **** This may be an old copy of the FAQ. It is only updated
- **** as and when Extension Shell is updated. Feel free to
- **** Archie for and download newer versions of the FAQ,
- **** but please don't email me to let me know - when I
- **** bring out a new release of Extension Shell, I'll
- **** update the FAQ with the more recent copy.
- ****
- **** -dair
- **** dair@kagi.com
- ****
-
-
-
-
-
- Answers to Frequently Asked Questions about writing System Extensions on
- the Macintosh Computer. Version 1.0.1 11/94
-
- This document is Copyright © 1994 by Brian Stern.
- I can be contacted by email at <BrianS@pbcomputing.com> on Internet.
-
- The purpose of this FAQ list is to provide information on the writing of
- system extensions. This arcane art is difficult to learn and the existing
- information on the subject is spread around in various places. Hopefully
- the information here will help new and old INIT writers to write better
- INITs in less time.
-
- The questions in this document are broken into two sections: System
- Extensions and Trap Patches. While most extensions contain trap patches
- these subjects seemed different so I have separated them.
-
- FAQ lists like this one are an integral part of Usenet. When I started
- reading Usenet news I assure you that I knew nothing about writing
- extensions. I learned by posting questions to comp.sys.mac.programmer and
- by reading other's questions and responses. Let me take this space to give
- you a few tips on phrasing of questions to newsgroups.
-
- When asking a question try to make the title as descriptive as possible.
- Most people don't have time to read every question posted and will ignore
- posts whose titles are unclear or meaningless. Don't post questions titled
- 'HELP, My program doesn't work', or worse 'URGENT NEED HELP with program'.
- I'm sure it's urgent to you, but not to me. Always indicate that you're
- asking a questing by including a (you guessed it) question mark. Consider
- these titles: 'Programmers make big salaries' and 'Programmers make big
- salaries?' Don't waste people's time with the former when you mean the
- latter. Another way to indicate a question is like this: '[Q] Programmers
- make big salaries?'. Try to include one of the words 'who, what, when,
- where, why, or how' in the titles of your questions. Your questions are
- much more likely to be answered by someone who actually knows the answer if
- your title is clear.
-
- The code samples in this document were developed with Think C. You may
- need to make some changes to use them with other development systems. I've
- used inline assembly in many of the code samples. To my eye this makes
- things more clear since it's obvious what's going on. Not everyone agrees
- with this and not all development environments provide inline assembly.
- It's possible to rewrite most or all of this code by use of inline
- functions. If you're writing in Pascal or using CodeWarrior you'll have to
- do it that way.
-
- You'll be able to find information on general questions of Mac programming
- in the FAQ list maintained by Jon Watte at:
- ftp://nada.kth.se/pub/hacks/mac-faq/CSMP_PD_FAQ
-
- If you have questions about writing extensions that aren't addressed in
- this document or any comments about it feel free to send them to me. If I
- know the answer or can find it out it may find its way into a later
- version.
-
- This document may be distributed freely as long as no changes are made to
- it. It may not be distributed in ways in which the user must pay for the
- document, other than reasonable download costs or costs for the medium,
- without the consent of the author.
-
- Thanks to the following people who provided sample code and made
- constructive comments: Pete Gontier, Dair Grant, Chelly Green, Devon
- Hubbard, Peter Lewis, Jim Walker, Jim Wintermyre.
-
- ---------------------------------------------------------------------
- System Extension Writing FAQ Outline:
-
- System Extensions:
-
- [1] What is an INIT, exactly?
- [2] Should I write an INIT?
- [3] Can I write an INIT that makes the application menu into a hierarchical
- menu?
- [4] Do I need to know assembler to write an INIT?
- [5] Can you show me a sample INIT?
- [6] Can I have global variables in my INIT?
- [7] How do I debug an INIT?
- [8] How do I write a Control Panel-INIT combination?
- [9] How do I capture keystrokes?
- [10] How can my extension get time periodically?
- [11] How should an INIT manage memory?
- [12] How do I get my INIT to turn itself off?
- [13] How do I get my INIT to show its icon like all the other cool INITs
- do?
- [14] How do I show a dialog from my INIT?
- [15] How do I maintain compatibility with future systems?
- [16] Any tips for INIT writing?
-
- Trap Patches:
-
- [17] What exactly is a trap patch?
- [18] What's the difference between a head patch and a tail patch?
- [19] How do I patch a trap?
- [20] How do I patch a register-based trap?
- [21] Can you show me a tail patch?[22] How do I patch a selector-based
- trap?
- [23] How do I patch a trap on the PPC?
- [24] Can I write a fat trap?
- [25] Tips?
- [26] What other sources of information are available?
-
- ---------------------------------------------------------------------
-
- [1] What is an INIT, exactly?
-
- An INIT is a type of code resource that is loaded into memory and executed
- during the startup process. They're called INITs because they're resources
- of type 'INIT'. INITs load at the end of the startup process, after the
- hardware checks have been done and the system has been started. The span
- of time during which INITs load and execute is known as INIT time.
- Applications don't load until after INIT time and in most cases the first
- application to load is the Finder.
-
- Because they load before all applications, INITs can modify the system in a
- way that affects all applications. They can add new functionalities and
- modify the way in which many processes occur on the Mac. Apple often
- supplies new additions and updates to system software as INITs. This
- includes things such as the Drag-and-Drop Manager, Thread Manager, Speech
- Manager, and Sound Manager 3.0. At some point these new features are
- rolled into a new software release and eventually they are included in new
- ROMs, but they all start life as INITs.
-
- Apple recommends that the term 'system extension' be used when
- communicating with non-programmers. This name carries the implication that
- they extend the functionality of the system. You'll see the terms 'INIT',
- 'extension', and 'system extension' used to mean the same thing throughout
- this document.
-
- Extensions are loaded in a defined order. Resources of type 'INIT' can be
- found in files of type 'INIT' (System Extension), 'cdev' (Control Panel),
- 'RDEV' (Chooser Device), 'appe' (application extension), and 'scri' (script
- system extensions). In order to be loaded, the INIT resource must be in
- one of these file types in one of several locations in the System Folder.
-
- Under system 7 INITs are loaded first from the Extensions Folder in
- alphabetical order. INITs present in script system extension files
- (filetype 'scri') load before INITs present in system extension files
- (filetype 'INIT'). Next, any INITs in the Control Panels folder are loaded
- in alphabetical order. Any INITs present at the root level of the System
- Folder are loaded after that. This order of loading is important if an
- extension is dependent on the presence of another extension. For example,
- if an INIT resource in an INIT file named 'SpeakAll' is dependent on the
- presence of the 'Speech Manager' extension, it either needs to have its
- name changed to something that sorts after 'Speech Manager' or it has to be
- located in the Control Panels folder, since extensions in the Control
- Panels folder load after extensions in the Extensions Folder.
-
- In system 6 the Extensions Folder and Control Panels Folders don't exist.
- Extensions are simply loaded in alphabetical order from the root level of
- the System Folder.
-
- --
- [2] Should I write an INIT?
-
- System extensions are not easy to write. If this is your first attempt at
- Mac programming, the answer to this question is NO. Many experienced Mac
- programmers have never written one and don't intend to.
-
- There are a number of other ways to add functionality without writing an
- extension. If you want to add functionality to a single application then
- think about writing an FKEY. These are invoked by hitting cmd-shift-number
- and perform an action at that time only. The standard screen shot
- (cmd-shift-3) is one example.
-
- Another user-invokable means of adding functionality is plug-in modules. A
- number of commercial software packages support plug-ins that can add
- functionality in a straightforward manner. This includes PhotoShop, Quark
- Express, Hypercard and others.
-
- Another means of adding functionality is the use of a background-only
- application, AKA faceless-background application (fba). Fbas are
- applications without a user interface. One type of fba is known as an
- application extension. These are applications whose resource files are of
- type 'appe'. They are placed in the extensions folder (or the Startup
- Items Folder) and are started up after INIT time. They can communicate
- with other processes by Apple Events and by Gestalt selectors. If you
- don't need to patch a trap then an fba may be the way to add functionality
- to the system. Since an fba runs as a normal process there are some things
- that it can do that system extensions cannot do (or at least cannot do
- safely), like launch applications and send and receive Apple Events. An
- INIT resource in the resource fork of an application extension in the
- Extensions Folder will load normally at INIT time. See issue 9 of develop
- and the Tech Note PS 2 - Background-Only Applications for more info.
-
- Also consider an application that is started up by being placed in the
- startup items folder. The Screen Saver 'Dark Side of the Mac' is written
- in this way. Screen Savers are traditionally written as extensions because
- extensions can have access to the mouse location and all keyboard events.
- However an intelligently written application can do the same and be more
- compatible.
-
- If none of these things seems like it will work for you and you are
- thinking something like 'I want X to happen every time a resource is
- loaded', or otherwise add to or replace the system's functionality in some
- way then an extension is probably what you need.
-
- Remember that when developing system extensions you will be working without
- a net.
-
- --
- [3] Can I write an INIT that makes the application menu into a hierarchical
- menu?
-
- Every couple of weeks someone posts a message on comp.sys.mac.programmer
- entitled 'Neat idea for an init'. These posts go on to describe some
- non-programmer's idea of a great INIT. Invariably these posts fall into
- two groups: those that have already been done, and those that should never
- be done. Think long and hard about the design of an extension before
- starting to write it. Consider your target audience. If it's only
- yourself then you can do what you want. My first couple of extensions were
- just tests to see if I could actually do it. The answer to the above
- question is: Maybe you can but probably you shouldn't.
-
- --
- [4] Do I need to know assembler to write an extension?
-
- Yes. Strictly speaking, the simplest extension that just beeps at startup
- and doesn't hang around past INIT time requires no assembler. However, any
- extension that does anything useful will require some assembler in order to
- patch a trap or install a jGNEFilter. The extension can be written mostly
- in a high level language like C or Pascal, but there will be bits and
- pieces that need to be written in assembler to keep the stack happy or to
- access parameters passed in registers. Hopefully there will be enough
- sample code in this document to get you on your way if your assembler is
- weak.
-
- You will often need to inspect the disassembled source of your extension.
- If your development environment doesn't allow this the ResEdit Code Viewer
- will also allow you to inspect code resources. It can be found at
- ftp://ftp.apple.com/dts/mac/tools/resedit/resedit-extensions.hqx.
-
- --
- [5] Can you show me a sample INIT?
-
- Here's the code for the 'Hello World' of INITs.
-
- void main(void)
- {
- SysBeep( 5 );
- }
-
- Not very complicated is it? There are a number of additional things that
- are required to make this work:
-
- 1) Set the project type to code resource.
- 2) Set the resource type to 'INIT'.
- 3) Set the resource ID to something (>= 128), and set the resource name if
- desired.
- 4) Set the resource attributes to 'System Heap'.
- 5) Set the resource attributes to 'Locked'.
- 6) Set the filetype of the built code resource file to 'INIT' with any
- creator signature.
- 7) Set the filename of the built code resource file to 'Hello World'.
- 8) Include a 'sysz' resource indicating a size of 100K.
-
- Let me explain what each of these things does.
-
- 1) Because you're building a standalone code resource your compiler needs
- to know this. This is how the compiler knows to use A4 addressing, rather
- than the A5 addressing used in applications.
-
- 2) As mentioned above, extensions are code resources of type 'INIT'.
-
- 3) Like any other resources they have IDs and can have names.
-
- 4) Setting the system heap flag places the code resource in the system
- heap. While not strictly required for this sample, in most other
- extensions we will want the code resource to remain in the system heap so
- this flag must be set. During the process that loads and executes INITs a
- small heap is created. Any memory allocation done by the INIT will, by
- default occur in this heap. Also any resources read into memory will, by
- default, go into this heap. If the system heap flag isn't set for the INIT
- itself then the INIT will be loaded into this temporary heap. When the
- INIT exits the heap is disposed and anything in it is lost. If an INIT
- intends to stay around past INIT time then it must have the system heap
- flag set.
-
- 5) The code resource should never move in memory so the Locked bit is set.
- When a resource with the Locked bit is loaded into memory it is loaded as
- low in the heap as possible. This is what we want since the INIT resource
- will never be unlocked. It is poor design to not set the Locked bit and to
- rely on the INIT to lock itself. In that case the INIT will not be loaded
- low in the system heap. This isn't fatal but will lead to fragmentation of
- the system heap.
-
- Some INITs try to use MoveHHi/HLock to move themselves or associated
- resources high in the system heap. MoveHHi is disabled for the system
- heap. Since the system heap is dynamically sizable the concept of the top
- of the heap is invalid. For this reason MoveHHi does nothing when the
- handle being referred to is within the system heap.
-
- 6) The filetype of 'INIT' gives you the generic extension icon, and of
- course allows the extension to load and execute at INIT time.
-
- 7) Set the file name to something like 'Hello World'.
-
- 8) A 'sysz' resource indicates to the INIT-loading code that your INIT
- requires a contiguous block of memory of the indicated size. The system
- heap will be expanded if necessary to make certain that a block of the
- requested size is available. If no 'sysz' recource is included the system
- assumes a block of 16K is sufficient. The format of the 'sysz' resource is
- simply a long integer indicating the blocksize. You can create one with
- ResEdit, or copy one from another INIT and edit it with the hex editor.
- This sample extension requires more than 16K because SysBeep allocates a
- sound channel and because you don't know what the size of the 'snd '
- resource is for the system alert sound. If no 'sysz' resource has been
- included this INIT sometimes doesn't work because there isn't enough room
- to allocate the sound channel.
-
- Once these things have been done you can build the code resource and drag
- its file to the System Folder. The Finder should place it in the
- Extensions Folder for you and it should have the generic extension icon.
- When you restart you will hear the beep when the extension is loaded.
-
- Every extension must have a routine called 'main'. When the INIT resource
- is loaded into memory during INIT time the system jumps to the beginning of
- the code resource. The header of the code resource will normally contain a
- branch instruction that branches to the main routine. This routine does
- whatever it needs to do and then returns. In this 'Hello World' extension
- all that the main routine does is call SysBeep. In more substantial
- extensions the main routine will do things like patch traps, install
- gestalt selectors, and load resources into memory.
-
- --
- [6] Can I have global variables in my INIT?
-
- Yes you can. The Think environment and the CodeWarrior environment use
- A4-based addressing for global variables in code resources. The base
- address of the extension is found in register A0 on entry to the extension,
- and routines are provided to save this value and to set up and restore
- register A4 when the INIT is entered later. (In fact the standard header
- installed by Think C in code resources loads the address of the code
- resource into A0 by use of pc-relative addressing.) See the A4-Addressing
- section of your development environment's manual for more information.
-
- When writing extensions with the MPW compilers you may need to use an
- A5-based method for accessing global variables. See the Apple tech note
- 'StandAlone Code, ad nauseam' (#256) for more information on this method.
- ftp://ftp.apple.com/dts/mac/tn/platforms.tools.pt/pt-35-stand-alone-code.hqx
-
-
- and
-
- "Another take on Globals in Standalone Code", Keith Rollin
- ftp://ftp.apple.com/dts/mac/docs/develop/develop.12.code/globals-in-standalone-code.hqx
-
- Here is some sample code using the Think C routines to illustrate this:
-
- /****Main*****************************************************/
- void
- main(void) //Main entry point of the extension
- {
- void *pToMe;
- Boolean initedOK;
-
- pToMe = GetA0(); //Save our address locally
-
- RememberA0(); //Save our base address
- SetUpA4(); //Set up A4-based addressing
-
- initedOK = InitAll(); //Patch traps etc.
- if ( initedOK )
- {
- //Make sure we stay around
- DetachResource( RecoverHandle( pToMe) );
- }
-
- RestoreA4(); //Reset A4 to its value on entry
- }
-
- This code sample also illustrates one method extensions can use to remain
- in memory after INIT time. Since extensions are code resources they will
- be removed from memory when their resource files are closed. This happens
- when the main routine exits. To avoid this, DetachResource must be called
- with the handle to the code resource. One common mistake with this is not
- having the code resource marked system heap. If it's not marked system
- heap it will be lost when the temporary heap is destroyed, whether it was
- detached or not. The GetA0 routine used above is defined in the tips
- section farther down in this document. Here are a few other methods for an
- extension to detach itself:
-
- void
- main(void) //Another method
- {
- asm
- {
- RecoverHandle //A0 already holdss our address
- move.L A0, -(A7) //DetachResource is stack-based
- DetachResource //so push A0 onto the stack
- }
- }
-
- void
- main(void) //This method uses no assembler
- {
- DetachResource( Get1Resource( 'INIT', kOurResID ) );
- }
-
- --
- [7] How do I debug an INIT?
-
- Debugging is generally the most time consuming, difficult, and all-around
- pain-in-the-neck part of producing good code. That goes double for INITs.
- Unfortunately many parts of INITs must be debugged with low level
- debuggers. The low level debuggers that I know about are MacsBug, TMON,
- and Jasik's Debugger. MacsBug is free and is available at
- ftp://ftp.apple.com/dts/mac/tools/macsbug/macsbug-6-5d6.hqx. The two other
- low level debuggers are commercialware. Jasik's debugger has the ability
- to do source level debugging of code resources, like system extensions.
-
- In most cases, your low level debugger of choice will load before INIT time
- so it can be used to debug initialization of INITs. One exception to this
- is Jasik's Debugger, which loads in two parts. One of these is an
- extension that must load before your extension.
-
- The debugger can be invoked by calling the Debugger() or DebugStr() traps.
- Placing these trap calls in your code will drop you into the low level
- debugger so that you can step through your code and inspect registers or
- memory as needed.
-
- One useful technique is to take advantage of Macsbug's ability to process
- commands after a semicolon in a DebugStr call. The following function can
- display information that you would otherwise have to hunt down using hex
- offsets:
-
- pascal void SomeFunction (arguments)
- {
- // ...
-
- asm { MOVE.L fooP, A0 } // fooP can be any type
- asm { MOVE.L sizeof(*fooP), D0 }
- DebugStr ("\p ; dm rA0 rD0"); // dump (*fooP) in hex
-
- // ...
- }
-
- If you haven't placed Debugger() calls in your code then you will have to
- set a breakpoint in order to step through any trap patches or other parts
- of your extension. Here are a couple of methods to do that. If you have
- patched a trap, say GetResource, if you drop into MacsBug and type 'il
- GetResource' MacsBug will begin to disassemble at your patch. You can then
- set a breakpoint by typing 'br GetResource' or br theaddress' where
- theaddress is the address in hex of your patch or some part of it. Type
- 'brc' when you want to clear all breakpoints. Using the A-trap commands
- will also work. 'atb GetResource' will set a break and 'atc' will clear
- all A-trap breakpoints.
-
- If you want to set a breakpoint in some other part of your extension, say
- in a jGNEFilter, then you need another way of finding its location in
- memory. In MacsBug type 'hx syszone^' or just 'hx' to set the current heap
- to the system heap (where your extension is located). Type 'br ' (don't
- hit return yet) and then type cmd-D to view the names of all the routines
- in the system heap that were compiled with MacsBug names turned on (like
- yours, right?) Scroll down until you find the name of your routine. Hit
- enter and the command line should look like 'br yourRoutineName'. Hit
- enter again and the breakpoint will be set. You can also simply type 'br
- yourRoutineName' and MacsBug will find it for you. The zone must be set to
- the zone containing the routine that you want to break on for MacsBug to
- find it, so make sure to set the zone to the system zone.
-
- Identifying your extension in the system heap is dependent on compiling it
- with the MacsBug symbols option turned on. This inserts Ascii versions of
- the names of each function in the compiled code in such a way the MacsBug ,
- and other debuggers can find these names. Jasik's debugger uses a
- different method for identifying your code that is based on SYM files,
- which are generated by your compiler. The SYM files allows Jasik's
- debugger to do source level debugging of INITs and other code resources.
-
- I have used a two-project method, when developing INITs and other code
- resources, that helps to cut down debugging time. Any extension consists
- of essentially two parts: the initialization portion and the implementation
- portion. The initialization portion detaches the resource as described
- above, shows the icon, and often patches traps. The implementation portion
- contains the actual trap patches. It is quite possible, and desirable, to
- test the implementation portion in the context of an application, rather
- than as an INIT. To accomplish this your project consists of three parts,
- which for a simple case would be three files.
-
- TesterApp Project:
- SetUpApplication.c Simple application shell that sets up the trap
- patches and provides a mechanism to call the traps
- TrapPatches.c Code for the trap patches
-
- INIT Project:
- SetUpINIt.c Standard INIT setup; contains main entry
- point; patches all necessary traps
- TrapPatches.c Code for the trap patches; same file as
- in the TesterApp Project
-
- The mechanism for calling the traps in SetUpApplication.c is usually a menu
- item. In some cases it might be a dialog with several buttons, each of
- which calls a particular trap. It isn't always necessary to patch traps in
- your TesterApplication. Simply calling the routines that implement the
- guts of the trap patches will often be just as good. Having two projects,
- one that builds the tester application and the other that builds the
- extension, allows you to save time debugging and to be able to build the
- extension at any time.
-
- Writing and testing extensions involves multiple rounds of
- 'compiling-installing the INIT-rebooting-Stepping through the INIT in a low
- level debugger'. Use of the two-project method will cut down this time.
-
- --
- [8] How do I write a Control Panel-INIT combination?
-
- System extensions generally have no interface. They do what they do
- quietly and the user doesn't want to hear from them. In many cases the
- user needs to set certain preferences. The natural mechanism for this is
- to couple a control panel with an INIT. The problem of course is how does
- the control panel communicate the changes to the INIT. I won't discuss the
- general mechanisms of control panel authoring here (see NIM: More Macintosh
- Toolbox, Chapter 8), but there are several mechanisms available for
- communicating between extensions and control panels.
-
- The simplest mechanism and one that will work in the vast majority of cases
- is for the extension to install a Gestalt selector. This selector returns
- the address of a block of memory that holds variables that control the
- actions of the extension. The control panel calls the Gestalt selector,
- retrieves the address of the memory block, and alters the values stored in
- the block as needed. In some cases the memory block can contain a function
- pointer to a function in the extension that needs to be called from the
- control panel. Here is some sample code:
-
- typedef struct CommonInfo{
- void (*ResetINIT) (void);//function pointer to reset
- //func
- Boolean On;
- };
-
- #define kSignature 'BLAH' //Should be the sig of the INIT
-
- static CommonInfo gInfo;
-
- /**InstallGestaltSelector****************************************/
- //In the INIT; runs at INIT time
- OSErr
- InstallGestaltSelector(void)
- {
- OSErr err;
-
- err = NewGestalt( kSignature, OurSelector );
-
- if ( err == noErr )
- {
- gInfo.ResetINIT = (void *) ResetFunction;
- gInfo.On = TRUE;
- }
-
- return err;
- }
-
- /**OurSelector**************************************************/
- //In the INIT
- pascal OSErr
- OurSelector( OSType theSelector, long *theResponse )
- {
- SetUpA4();
-
- *theResponse = (long) &gInfo;
-
- RestoreA4();
-
- return noErr;
- }
-
- /**ResetFunction**************************************************/
- //In the INIT
- void
- ResetFunction(void)
- {
- SetUpA4();
-
- //Do something here
-
- RestoreA4();
- }
-
- /**Close********************************************************/
- //In the Control Panel
- void
- Close( Boolean IsOn )
- {
- long result;
- CommonInfo *Info;
-
- //Get address of globals struct from init
- err = Gestalt( kSignature, &result );
- if ( err == noErr )
- {
- Info = (GlobalsType *) result;
- Info->On = IsOn; //Reset OnOff Boolean
- ( * (*Info).ResetINIT) (); //Jump to INIT
- }
- }
-
- If it is possible for an error to occur that prevents the extension from
- resetting itself the ResetFunction should return an error code and the
- control panel should display an alert indicating the problem.
-
- Two additional methods are sometimes used to accomplish communication
- between an extension and a control panel:
-
- The first of these is to write a driver that is installed by the extension.
- The driver holds global variables and will return the address of the block
- of memory holding these variables in response to i/o, status, or control
- calls to the driver. Sample code showing how to do this is available in a
- package called driver-22 written by Pete Resnick to be found at:
- ftp://sumex-aim.stanford.edu/info-mac/dev/src/driver-22-c.hqx.gz
-
- The second additional method is to use the PPC toolbox for direct
- communication. There is sample code demonstrating this at:
- ftp://ftp.apple.com/dts/mac/sc/7.0.samples/init-cdev.hqx.
-
- --[9] How do I capture keystrokes?
-
- This is accomplished by writing a jGNEFilter function. jGNEFilter
- functions are called from GetNextEvent and WaitNextEvent just before those
- traps return to an application. They are passed a pointer to the event
- record that will be returned to the application. In order to capture
- keystrokes the jGNEFilter would simply check the what field of the event
- record looking for keyboard events and would extract the information from
- the message field if one were found.
-
- The jGNEFilter mechanism is very powerful and is one way that screensavers
- can be implemented. The filter would save the time of any keyboard events
- and would compare the location of the mouse against its previous location
- on null events. If the preset time had elapsed during which no keyboard
- events or mouse movement had occurred then the screen saver would activate.
- A more complete discussion of the jGNEFilter is in the next section.
-
- If you are thinking of patching WaitNextEvent or PostEvent in order to
- capture keystrokes or other events, don't. Use a jGNEFilter instead. It's
- easier, it's compatible. It's even documented. See the Tech Note
- 'GetNextEvent; Blinking Apple Menu' (#85).
-
- ftp://ftp.apple.com/dts/mac/tn/toolbox.tb/tb-11-getnextevent.hqx
-
- --
- [10] How can my extension get time periodically?
-
- Installing a jGNEFilter is one method of obtaining periodic time. Since
- the jGNEFilter mechanism is dependent on the event processing mechanism,
- one problem is that no events may be posted if the mouse is held down for
- an extended time.
-
- If your INIT only needs to get time every once in a while then I recommend
- that it only do its thing on null events. On other events it should just
- return. This will have the least impact on the machine's performance.
-
- Remember that your jGNEFilter will be called for EVERY event on the
- machine. Do not do a lot of processing on every event or you will be
- slowing down the machine needlessly. Another way to reduce the frequency
- of your extension's processing is to use a simple timer, based on
- TickCount(). In this way your processing is only done, say, every 30 or 60
- ticks, on null events of course.
-
- Sample code demonstrating jGNEFilters can be found in a package called jGNE
- Helper by Pete Gontier in the alt.sources.mac archive at:
- ftp://ftpbio.bgsu.edu/ftp/pub/alt.sources.mac/vol-01/jgnehelper.cpt.hqx.
-
- Another sample in MPW assembler is at
- ftp://ftp.apple.com/dts/mac/sc/snippets/toolbox/jgnefilter.hqx.
-
- Here's another example that works in Think C:
-
- static ProcPtr gOldGNEFilter;
-
- /****InstallFilter***********************************************/
- //Run this at INIT time
- void
- InstallGNEFilter (void)
- {
- /*Save the ProcPtr to the previous jGNEFilter and insert ours*/
- gOldGNEFilter = JGNEFilter;
- JGNEFilter = (ProcPtr) StripAddress( FilterProc );
-
- }
-
- /****FilterProc**************************************************/
-
- void
- FilterProc(void)
- {
- EventRecord *theEvent;
- long SaveD0;
-
- theEvent = GetA1(); //Move the eventPtr to a variable
- SaveD0 = GetD0(); //Preserve D0
-
- SetUpA4();
-
- switch ( (*theEvent).what ) {
- case nullEvent:
- //Do our thing
- break;
-
- case keyDown:
- //Do something else
- break;
- }
-
- //Execute the previous jGNEFilter
- SetA1( theEvent ); //Restore A1 for the next jGNEFiler
- SetD0( SaveD0); //Restore D0
- SetA0( gOldGNEFilter ); //Put next jGNEFilter in A0
-
- RestoreA4();
-
- asm{
- Unlk A6
- Move.W D0, 4(A7) ;Set Function result on the stack
- JMP (A0) ;Jump to the next jGNEFilter
- }
-
- }
-
- Since a jGNEFilter has access to the actual event record that will be
- returned to the application, the filter can alter the event. The most
- common thing to do is to 'cancel' an event by changing it to a null event
- (Ex. theEvent->what = nullEvent). In this case it should also set the
- value in register D0 to False, or zero.
-
- Here are some additional methods for an extension to get time periodically:
-
- If your extension needs more frequent or regular time then can be provided
- by a jGNEFilter then you can install a VBL task or a time manager task.
- Since these run at interrupt time they cannot do anything that could move
- or purge memory. Other methods include patching a trap that is called
- frequently, like SetPort.
-
- A faceless Notification Manager request is another method to get time.
- This is a request that has all the fields in the notification record set to
- NULL except the nmResp field that holds the address of your routine to be
- executed. Your notification response routine will be called soon and will
- be able to move memory. If necessary the routine can reinstall itself.
- This technique is useful for tasks that need to be executed once or
- intermittently. For those tasks that need to be executed regularly use one
- of the other techniques.
-
- You could of course have a faceless NM request that is installed by a VBL
- task or a time manager task.
-
- Use of a faceless background application in concert with an extension is
- yet another method to get time. The fba would do its work on its null
- events.
-
- --
- [11] How should an INIT manage memory?
-
- In general, at INIT time extensions will want to allocate memory in the
- system heap. If you are allocating pointers or handles, use the SYS
- variants, like NewHandleSys() and NewPtrSys(). It is a common error to
- forget this and then to wonder why the INIT crashes. If you call NewHandle
- at INIT time, the handle will be allocated in the temporary heap allocated
- for your extension. If your INIT attempts to use it after INIT time the
- temporary heap will be long gone, along with any handles or pointers that
- had been allocated in it. Any attempt to use handles or pointers that no
- longer exist are predictably unpredictable :-)
-
- NewHandle and NewPtr will allocate their memory blocks in the system heap
- if the zone has been set to the system heap, as shown in the next
- paragraph.
-
- If you need to read in resources you can ensure that they go into the
- system heap with something like the following code:
-
- THz saveZone = GetZone();
- SetZone( SystemZone() );
- //read in resources
- SetZone( saveZone );
-
- You will of course need to detach the resources if they need to remain in
- memory after your extension exits. The resource file containing your
- extension will be closed when your extension exits.
-
- If you need to read resources into memory after INIT time you need to
- decide which heap they should go into, either the application heap or the
- system heap. It's a bit hard to make a specific recommendation on this but
- if the resource is something that the application is expecting to be read
- in then it should go into the application heap. This would include things
- like WIND resources in a trap patch to GetNewWindow(). The problem of
- course is that the application heap may not have enough room. However, if
- the resources are private to the extension then they should go into the
- system heap.
-
- It should go without saying that you should always check the error codes on
- memory allocating calls and resource manager calls. If your extension is
- doing something in response to a user action, say making a network
- connection, then it is appropriate to report such errors. In many cases
- however, your extension will simply do nothing in the case of out of memory
- errors or missing resource errors. It is best to attempt to allocate all
- of these things at INIT time and if unsuccessful to bail out then.
-
- As mentioned above, MoveHHi doesn't work in the system heap. If you intend
- to allocate a handle that will remain locked for extended periods, then
- call ResrvMem before allocating and locking the handle. This will place
- the handle low in the system heap and help to prevent heap fragmentation.
-
- Many toolbox calls are documented as not moving or purging memory and as
- being safe to call at interrupt time. If you are patching one of these
- traps then you must preserve this property. You are guaranteed to cause
- other software to crash if you don't, and your users will hate you (once
- they figure out that it's you). Be aware that the only memory manager
- routine safe to call under these circumstances is BlockMove. Also be aware
- that it is unsafe to access an unlocked handle at interrupt time. It is
- possible that the memory manager is in the midst of moving it from one
- place to another in the heap, and the master pointer may not be updated
- yet.
-
- --
- [12] How do I get my INIT to turn itself off?
-
- An extension might want to turn itself off at INIT time based on its
- preferences setting, or based on a key being pressed or the mouse button
- being pressed, or due to an error during initialization. Extensions
- usually show an icon with a red X through it in this case. An extension
- might also want to turn itself off temporarily after INIT time in response
- to its Control Panel. For instance GateKeeper has an on/off switch that
- turns off virus checking for a set time.
-
- The strategy for temporarily turning off an extension is simply to set a
- global flag and to check it from within the trap patches or other parts of
- the extension. If the flag is off then the trap patch simply executes the
- previous trap. It is generally unsafe to unpatch or patch traps after INIT
- time from an extension. The reason for this is that if another extension
- patches the same trap after you have, then it will be jumping to your patch
- when it has completed its work. If you have removed your patch then this
- calling chain will be disrupted and bad things will happen. Also, the
- Finder patches various traps when it loads, which is after INIT time.
- Disrupting those patches would be a very bad thing.
-
- If an extension determines at INIT time that it isn't going to stay around
- then it shouldn't call DetachResource on itself. It's best that your
- extension determine that anything it's dependent on, such as resources,
- specific system Managers, and sufficient memory, are present *before* it
- starts to patch traps and install drivers, jGNEFilters and so on. It would
- be a very bad idea for an extension to not detach itself after it had
- already patched a trap if the trap patch resided in the extension.
-
- Many extensions use a particular key press as a signal to indicate that the
- user wants them not to run. Of course the system uses the shift key as a
- signal not to turn on any extensions so you can't use that. Some
- extensions use the option or command keys for this purpose. The problem
- with using those keys is that every time I rebuild the desktop those
- extensions are needlessly inactivated. I recommend that the space bar be
- used for this purpose. Here's some sample code:
-
- Boolean
- SpaceBarIsDown(void)
- {
- KeyMap theKeys;
-
- GetKeys( theKeys );
-
- if ( theKeys[1] & 0x00000200 )//Check for spacebar
- return TRUE;
- else
- return FALSE;
-
- }
-
- --
- [13] How do I get my INIT to show it's icon like all the other cool inits
- do?
-
- This is easy. There is code available that does this for you. Get a
- package written by Jim Walker called ShowIcon7 at:
- ftp://mac.archive.umich.edu/mac/development/source/showicon7.sit.hqx
-
- You use it essentially as a plug-in. Just pass in the icon's resource ID
- and it does everything for you. It also shows how to set up an A5 world in
- an extension. You might also take a look at Dair Grant's Extension shell
- package. This one shows how to do animated icons.
-
- If your extension decides that it can't install itself then it passes the
- resource ID of an icon that has a red X through it to the showicon7 code
- resource.
-
- Some extensions include the ShowIcon code within their own code resources.
- This code is only about 1K but it seems pointless to me for this code to
- sit in the system heap when it doesn't have to be. Use it as a plug-in for
- your extensions.
-
- --
- [14] How do I show a dialog from my INIT?
-
- First of all, I hate windows of any kind during the startup process. If
- all you want to do is show an alert then use the Notification Manager.
- Your alert will show up when the Finder starts but the user will see it and
- will get whatever message you need to send.
-
- The problem I have with windows at INIT time is that they slow down this
- process and require user interactivity in a process that shouldn't do so.
- Consider the computer that is on 24 hours a day doing something important
- unattended. The power goes off and when it comes back on the machine
- reboots. The user returns several hours later to find that the machine is
- still in the middle of its startup because your alert is waiting for a
- response. Consider also the hapless INIT writer who has to reboot his
- machine 20 times a day. Your INIT will not last long on my, um, his
- machine.
-
- One additional annoyance is that you need to call InitWindows to show your
- window and this erases all the nice INIT icons on the screen. I hate that.
-
- Here are a few possible alternatives.
-
- * Play a sound or use the Speech Manager to communicate the information.
-
- * Show a different icon during startup to indicate an error. An icon with
- a red X through it is one way to do this. You could also use animated
- icons.
-
- * If you must put up an alert then use a timer so that the alert goes away
- by itself, even if the OK button isn't clicked.
-
- * If you need to interact with the user to get some information, say a
- password for a network connection, then do this once and save the results
- in a preferences file. Provide a Control Panel to change the information.
- Think of Control Panels as the interface for extensions.
-
- * Store descriptions of any errors that occur in a preferences file. Have
- the Control Panel display this information. Remember to clear this
- information on each restart and to indicate to the user by sound or icon
- that an error has occurred.
-
- * If you need to communicate error information to the user after INIT time
- then you should definitely use the Notification Manager. For example,
- MacSLIP uses the NM to show an alert indicating that the carrier has been
- lost.
-
- If you still want to show a dialog at INIT time then you need to set up an
- A5 world first. The ShowIcon7 code mentioned above shows how to do this.
- Also see the Tech Note 'Stand-Alone Code' (#256) for an explanation and
- sample code for this and 'Giving the (Desk)Hook to INITs' (247) discusses a
- bug that can appear when showing windows from extensions.
- ftp://ftp.apple.com/dts/mac/tn/operating.system.os/os-02-deskhook-and-init.hqx
-
- If you do use the Notification Manager from an extension to indicate that
- the extension couldn't load you should use a self-disposing Notification
- request. There are several strategies for doing this. I have some code
- samples for this that will be (have been?) posted to alt.sources.mac soon.
-
- --
- [15] How do I maintain compatibility with future systems?
-
- This is tough, and the short answer is that you probably don't. You'll
- notice that Apple comes out with new versions of its extensions with each
- revision of the system. Apple's extensions also eventually disappear as
- their functionality is rolled into the system. In all likelihood you'll
- have to come out with new versions of your extensions as new versions of
- the system come out as well.
-
- Having said all that, there are things you can do to minimize this problem.
- Here are a few suggestions:
-
- * Minimize your reliance on undocumented features of the system.
-
- * Minimize your reliance on low memory globals. Use the Universal Header
- access 'functions' for accessing the low memory globals if necessary.
-
- * Use system features like Gestalt, the Process Manager, and the
- Notification Manager to get information about the system, and to
- communicate with the user.
-
- * Try to patch as few traps as possible and use non-patching methods
- whenever possible (e.g., use a jGNEFilter instead of patching
- WaitNextEvent; the filter is more likely to remain compatible than your
- patch).
-
- * Don't use self-modifying code. Self-modifying code changes the
- instructions from what they were compiled as, to something else, at
- run-time. The classic example is to change the address in a JMP
- instruction at run-time so that it jumps to the address of the previous
- trap. This may seem faster than using a global variable but it is only
- slightly faster. It is harder to write, debug, and maintain self-modifying
- code, and it is definitely more likely to break with new system releases.
- If you do use self-modifying code, remember to flush the cache. See the
- tech note 'Cache As Cache Can' (#261) for more information on that subject.
-
- * Use the Universal Headers for writing your extensions. This will help to
- ease the transition when it comes.
-
- --
- [16] Any tips for INIT writing?
-
- You may not want your INIT to actually do anything until after INIT time.
- You can find the end of INIT time if you have a jGNEFilter installed when
- the first null event occurs. The Notification Manager doesn't usually
- start processing requests until INIT time is over so posting a faceless
- notification request is another method to find the end of INIT time
- (probably the best if you don't need a jGNEFilter for something else). You
- can also patch Launch to find the end of INIT time but this is trickier.
-
- Unfortunately some extension writers do put up windows during INIT time.
- Because of this it's possible that events will occur before INIT time is
- over. To fail-safe the above approaches you also need to check for the
- presence of the Process Manager with a Gestalt call. The Process Manager
- isn't available until after INIT time. If Gestalt reports that the Process
- Manager is not available then you need to reinstall your NM request, or
- simply wait for another event to be reported to your jGNEFilter when the
- Process Manager is available.
-
- Here are some utility routines that can reduce the need for 68K assembler
- in your extensions:
-
- pascal void SetA0( void* ) = { 0x205F };
- pascal void SetA1( void* ) = { 0x225F };
- void * GetA0( void ) = { 0x2008 };
- void * GetA7( void ) = { 0x200F };
-
-
- -----------------------------------------------------------------------
-
- Trap Patches:
-
- --
- [17] What exactly is a trap patch?
-
- A trap patch is a method for changing the functionality of a trap. The
- addresses of all the traps are maintained in the two trap dispatch tables.
- By using the routine NSetTrapAddress and friends you can change the address
- of a particular trap to code that you provide. When this is done at INIT
- time, all calls to the patched trap from all applications will go to your
- code, which in most cases will do something and then call through the
- existing trap in the ROMs. Be warned that the Finder patches some traps
- when it starts up in a way that prevents previously-installed patches from
- executing.
-
- I recommend that you read the descriptions of the trap dispatch mechanism
- in the 'Using Assembly Language' chapters in IM I and IV and also in the
- 'Trap Manager' chapter in NIM Operating System Utilities.
-
- Traps come in several types based on their parameter passing conventions.
- Most toolbox traps use pascal calling conventions, which means that all
- parameters are passed on the stack and the return value, if any, is placed
- on the stack.
-
- Some traps use register-based calling conventions. In these traps the
- parameters are passed in registers and the return value is returned in a
- register, usually D0. For example all the Memory Manager traps are
- register-based and the File Manager traps are also register-based.
-
- Some traps are selector-based. There are only so many spots in the
- trap-dispatch tables. In order to preserve space in these tables
- selector-based traps have been developed. In these traps a single trap
- serves as the front end for a number of system routines. The parameters of
- these traps are passed in the usual manner, either on the stack or in
- registers, and a selector is also passed, usually in a register. When the
- trap is called it checks the selector and then dispatches to the
- appropriate routine. Patching each of these types of traps involves
- different mechanisms. We'll look at samples of each one.
-
- Patching traps on the PowerMac is a bit different than on the 68K Macs.
- Most of the discussion here is aimed at patching traps on the 68K Macs.
- Hopefully I'll learn some more about this subject soon and there will be
- some better info here on patching traps on the PowerMac.
-
- --
- [18] What's the difference between a head patch and a tail patch?
-
- It is most common to add some functionality to a trap when patching it
- rather than just replacing the existing trap. For instance I've written an
- extension that speaks the text in alerts by using the Speech Manager. This
- works by patching Alert and friends. When Alert is called the patch gets
- the text that appears in the alert and passes it to the Speech Manager.
- The patch then calls the existing Alert trap that is in the ROMs. A patch
- that works in this way is called a head patch; it does its business and
- then it calls the previous trap.
-
- A tail patch is a bit different. A virus-checking program might want to
- patch GetResource and then examine the resource that was read in to see if
- it contains a virus. In order to do this the patch must first call the
- existing trap and then do its processing, and finally return to the
- application. This is a tail patch because some processing occurs after the
- existing trap is called. In order for a patch to be a head patch you must
- use a jmp instruction to jump to the previous trap. If you use a jsr or a
- C function pointer to jump to the previous trap you have a tail patch.
-
- The reason that this head and tail patch business has been so important in
- the past is because of an Apple invention called the come-from patch.
- Patches were invented, of course, so that Apple could fix bugs in the ROMs
- and could update the routines in the ROMs with software. This is why you
- can run system 7 on a Mac Plus, whose ROMs are obviously missing most of
- the additions made to the toolbox in recent years.
-
- Certain ROM routines are particularly large so it is inconvenient to patch
- them if they have bugs in them. To get around this problem the Apple
- programmers searched for smaller routines that are called from the large
- buggy routines and placed patches in the smaller routines. These patches
- check the return address on the stack. If it is the address of the buggy
- routine then a fix is applied. If not then they just go on as usual. The
- patches to these smaller routines are known as come-from patches. If you
- tail patch one of these and then call the existing come-from patch, the
- return address on the stack will be in your patch and not the buggy routine
- that called you. In this case the come-from patch will not apply its fix
- and your system will crash.
-
- The good news is that as of system 7 it is safe to apply tail patches. The
- come-from patches still exist in system software, but NGetTrapAddress has
- been modified to return an address that is safe to use when applying
- tail-patches. However, if you wish your extension to run in System 6 then
- tail-patches are not allowed. This is documented in the Trap Manager
- chapter in NIM: OS Utilities.
-
- If you absolutely positively need a tail patch in System 6 then the
- following logic may apply: (Just don't tell anyone that I told you this :-)
- Apple is not releasing any new versions of System 6 so no new come-from
- patches will be forthcoming for System 6. If you do careful testing of the
- traps you wish to tail-patch you will probably be OK. In general traps not
- called from the ROMs, like Alert and MenuKey, will not contain come-from
- patches.
-
- One final thing: In system 7 it is safe to apply tail-patches to all traps
- except FrontWindow.
-
- --
- [19] How do I patch a trap?
-
- Here is some sample code for a head patch of Alert, a stack-based trap:
-
- TrapPtr gOldAlertTrapAddress;
-
- /****InstallPatch***************************************************/
-
- void
- InstallPatch (void)
- {
- gOldAlertTrapAddress = GetToolTrapAddress( _Alert );
- SetToolTrapAddress( (long) AlertPatch, _Alert );
- }
-
- /****AlertPatch***************************************************/
-
- pascal void
- AlertPatch( short alertID, ProcPtr filterProcPtr )
- {
- SetUpA4();
-
- MyAlert( alertID) ; //Do our thing
-
- //store the correct alert addr
- //in A0 while we can still access globals via A4
-
- asm { move.l gOldAlertTrapAddress, A0 }
-
- RestoreA4();
-
- asm {
- unlk A6 //match the link generated by C
- jmp (A0) //jump to _Alert
- }
- }
-
- There are a number of details to note here. This patch is of course part
- of a code resource that is loaded at INIT time and detached as described in
- an earlier section. The routine InstallPatch must be called at INIT time.
-
-
- The routines GetToolTrapAddress and SetToolTrapAddress allow you to get and
- set the addresses of Tool Traps. The similar routines GetOSTrapAddress and
- SetOSTrapAddress allow you to manipulate the addresses of OS traps. The
- routines NGetTrapAddress and NSetTrapAddress allow you to manipulate the
- addresses of either, although they call glue code. I recommend that you
- use the GetXTrapAddress and SetXTrapAddress calls. There are two obsolete
- calls: GetTrapAddress and SetTrapAddress. Don't use them.
-
- The prototype for this patch is declared as 'pascal void' while the
- prototype for Alert is 'pascal short'. Because this is a head patch it
- will not be returning a result; the result will be returned from the real
- Alert trap. The pascal keyword is used to indicate pascal calling
- conventions. It is not strictly required in all cases but does no harm.
-
- The Think C routines SetUpA4 and RestoreA4 are called to allow access to
- global variables by A4 addressing. In this case gOldAlertTrapAddress is
- the only global variable we are addressing, unless any are used inside
- MyAlert. Note that this variable is moved to A0 while access to global
- variables is still available. In some cases one might move a global
- variable to a local variable, which doesn't rely on A4 addressing. A4
- could then be restored and the old trap address could be loaded into A0
- later in the code.
-
- Because there is a parameter list the compiler generates a Link A6
- instruction at the start of this function. In order to restore the stack a
- matching Unlk A6 must be placed at the end of the function. Since we are
- exiting by the jmp (A0) we must insert the Unlk A6 ourselves. The compiler
- does generate an Unlk A6 and an RTS at the end of this function, but they
- will never be executed. The presence of the Link A6 instruction is
- dependent on the particular compiler you use and on its rules for
- generating a stack frame. It is a good idea to disassemble the code for
- your trap patches to see whether a stack frame has been generated in order
- to determine if you need to insert the Unlk A6 instruction. If you don't
- match the Link A6 with an Unlk A6 the stack will be screwed up.
-
- It is essential that the stack look exactly the same on exit from a head
- patch as it does on entry. If not you will surely crash. (If you had good
- reason you could modify the value of a parameter on the stack, but that's
- another story.) This patch saves and restores A4 but does modify A0 and A1
- (SetUpA4 uses A1). In general, with head patches of stack-based traps you
- can modify A0, A1, D0, D1, and D2, but not any other registers. You may
- need to look at the disassembled code to be sure that all your registers
- are properly saved and restored.
-
- The design of the patch as shown here, with the patch code calling a
- separate function to perform the actual functionality of the patch is a
- good design to follow with all but the simplest of patches.
-
- You might think that you need to call StripAddress on the address of the
- patch routine before passing this address to SetToolTrapAddress. This is
- not necessary unless the address is actually a handle. If you were to load
- a code resource and pass its entry point to SetToolTrapAddress then it
- would need to be stripped.
-
- --
- [20] How do I patch a register-based trap?
-
- Here is the code for a sample register-based trap patch:
-
- TrapPtr gMountVolAddress;
-
- /****InstallPatch**************************************************/
-
- void
- InstallPatch(void)
- {
- gMountVolAddress = GetOSTrapAddress( _MountVol );
- SetOSTrapAddress( (long) MountVolPatch, _MountVol );
- }
-
- /****MountVolPatch**************************************************
-
- This is a register-based trap that has A0 set to point to its parameter
- block on entry. The prototype for MountVol is:
-
- pascal OSErr PBMountVol( ParmBlkPtr paramBlock )
-
- This patch beeps when a floppy or CD-ROM is inserted or when a
- harddrive is mounted by the Finder.
-
- *******************************************************************/
-
- pascal void
- MountVolPatch(void)
- {
- //Save some registers
- //Save A0 since it's trashed by SetUpA4
- //D1 contains the trap word
-
- asm { movem.l a0/d0-d1, -(sp) }
-
- SetUpA4(); //Allow access to global variables
-
- SysBeep( 5 ); //The guts of our head patch
-
- //store the correct MountVol addr
- //in A1 while we can still access globals via A4
- asm { move.l gMountVolAddress, A1 }
-
- RestoreA4(); //Restore previous value in A4
-
- asm { //Restore the registers
- movem.l (sp)+, a0/d0-d1
- jmp (A1) //jump to _MountVol
- }
-
- }
-
- This head patch is similar in structure to the patch to Alert with a few
- differences. The prototype uses no parameters and has no return value. The
- single parameter is passed through A0. This patch doesn't do anything with
- this value but it could be moved to a local variable and then used to
- reference the fields in the parameter block if desired. Access to global
- variables is by the same A4 mechanism as in the Alert patch. Note that
- _MountVol is an OS trap so GetOSTrapAddress and SetOSTrapAddress are used
- to set up the patch.
-
- Since A0 is used to pass the parameter to this trap we jump to the real
- _MountVol trap through A1.
-
- Obviously A0 must be saved and restored in this patch. OS traps expect to
- find the trap word in D1 so it must be saved and restored as well. Three
- registers, A0, D0, and D1, are saved onto the stack with the movem
- instruction, and restored at the end of the patch.
-
- Think C doesn't generate a 'Link A6' at the start of this function because
- there are no parameters and no local variables. Because of this no 'Unlk
- A6' is needed at the end of the function.
-
- --
- [21] Can you show me a tail patch?
- Here is another example of a patch to a register-based trap. This sample
- is a tail patch and is dependent on the CodeWarrior environment. Because
- CW allows you to specify that parameters are passed in registers this trap
- patch requires no assembly.
-
- extern pascal OSErr (*Old_MountVol)( ParmBlkPtr pb : __A0 ) : __D0;
-
- pascal OSErr My_MountVol( ParmBlkPtr pb : __A0 ) : __D0
- {
- OSErr err;
- long saveA4 = SetCurrentA4();
-
- err = Old_MountVol( pb );
- DoSomthingFunc();
-
- SetA4( saveA4 );
- return err;
- }
-
- --
- [22] How do I patch a selector-based trap?
-
- Here is a sample patch to PrGlue. This trap is the front end for all the
- Printing Manager routines. Its selector is pushed on the stack
-
- typedef struct
- {
- long Selector;
- THPrint hPrint;
- } PrJobDialogStack;
-
- TrapPtr PrGlueAddress;
-
- /****InstallPatch****************************************************/
-
- void
- InstallPatch(void)
- {
- PrGlueAddress = GetToolTrapAddress( _PrGlue );
- SetToolTrapAddress( (long) PrGluePatch, _PrGlue );
- }
-
- /****PrGluePatch****************************************************
-
- This is a stack-based trap with a long word selector also pushed
- onto the stack. On entry the selector is at 4(A7). The return address is
- at 0(A7). After the 'Link A6' the selector is at 12(A7).
-
- *******************************************************************/
-
- #define kSelectorOffset 12
- #define kPrJobDialogSelector 0x32040488
-
- pascal void
- PrGluePatch(void)
- {
- PrJobDialogStack *StackPtr;
-
- //Get address of the stack frame
- //and save it in a local variable
-
- asm {
- lea kSelectorOffset(A7), A0
- move.l A0, StackPtr
- }
-
- SetUpA4(); //Allow access to global variables
-
- //Check the selector
- if ( StackPtr->Selector == kPrJobDialogSelector )
- {
- SysBeep( 5 );
-
- //Pass hPrint to our function to do something
- DoSomethingFunc( StackPtr->hPrint );
- }
-
- //Store the correct PrGlue addr
- //in A0 while can still access globals via A4
- asm { move.l PrGlueAddress, A0 }
- RestoreA4(); //Restore previous value in A4
-
- asm {
- unlk A6 //match C's Link A6
- jmp (A0) //jump to _PrGlue
- }
-
- }
-
- In order to access the selector and the parameters for PrGlue we use a
- pointer to a struct. Once the pointer is initialized correctly we can
- access the selector and any parameters from C easily.
-
- According to NIM: PPC System Software it is not safe to patch
- selector-based traps with PPC native code. All patches of selector-based
- traps on the PowerMac should be written in 68K code.
-
- --
- [23] How do I patch a trap on the PPC?
-
- See NIM 'PowerPC System Software' for a more complete discussion. There is
- also a new book by Tom Thomson called 'Power Macintosh Programming Starter
- Kit' that has examples of how to patch traps on the PowerMac.
-
- Patching traps on the PowerMac is similar to patching on the 68K
- architecture. Of course you must generate a UniversalProcPtr for each of
- your patches in the system heap, and these are then passed to the
- SetXTrapAddress routines. Since code fragments have their own globals the
- use of A4 or A5-based mechanisms for accessing global variables isn't
- needed. In order to call the previous trap you need to call
- CallUniversalProc or CallOSTrapUniversalProc and return its result from
- your patch. As a result all patches on the PowerMac are tail patches.
-
- You cannot safely patch a selector-based trap in native code. See NIM PPC
- for an explanation of this. Unless or until this changes you shoud do all
- patching of selector-based traps in 68K code.
-
- You may find an application called 'Traps Check' useful. This app supplies
- a report about all the traps on a Powermac, indicating whether each trap is
- emulated or native. Another way to do this is to drop into MacsBug and
- disassemble from the address of the trap you're interested in (e.g., 'il
- CopyBits' ). For traps that are native you'll see a routine descriptor
- that begins with the MixedModeMagic trap (AAFE). This of course won't tell
- you if the trap has been patched. You can identify a patch by whether it's
- in RAM or ROM, from its address. Determining whether a patched trap is PPC
- native or not may take some additioinal sleuthing. You can find Traps Check
- at: ftp://sumex-aim.stanford.edu/info-mac/dev/traps-check-10.hqx
-
- Here is a sample PowerMac trap patch for GetResource:
-
- enum {
- uppGetResourceProcInfo= kPascalStackBased
- | RESULT_SIZE(SIZE_CODE(sizeof(Handle)))
- | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(ResType)))
- | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(short)))
- };
-
- UniversalProcPtr gGetResourceUPP;
- UniversalProcPtr gGetResourcePatchUPP;
-
- /****InstallPatch**************************************************/
-
- void
- InstallPatch(void)
- {
- THz saveZone = GetZone();
- SetZone( SystemZone() ); //put UPP in syszone
-
- gGetResourceUPP = GetToolTrapAddress( _GetResource );
-
- gGetResourcePatchUPP =
- NewRoutineDescriptor( (ProcPtr) GetResourcePatch,
- uppGetResourceProcInfo, GetCurrentISA() );
-
- SetToolTrapAddress( gGetResourcePatchUPP, _GetResource );
-
- SetZone( saveZone );
-
- }
-
-
- /****GetResourcePatch*********************************************/
-
- Handle
- GetResourcePatch( ResType theType, short theID )
- {
- Handle result;
-
- //We don't need no 'DUMB' resources
- if ( theType == 'DUMB' )
- result = NULL;
- else
- result = (Handle) CallUniversalProc( gGetResourceUPP,
- uppGetResourceProcInfo, theType, theID );
-
- return result;
-
- }
-
- This patch as written is PPC only for illustrative purposes. In real life
- the NewRoutineDescriptor and CallUniversalProc would be #defined in a
- header file and would compile correctly for both 68K and PPC code. You can
- find examples of how to do this in the Universal header files.
-
- --
- [24] Can I write a fat trap?
-
- //Under construction
-
- --
- [25] Tips?
-
- If your patch isn't called you may have guessed wrong on whether it's a
- ToolTrap or an OSTrap. The high bit of the second byte of the trap word is
- set for ToolTraps. The following function can be used to get the correct
- trap address for both ToolTraps and OSTraps.
-
- pascal void * GetCurrentTrapAddress( unsigned short trapWord )
- {
- if ( trapWord & 0x0800 )
- return GetToolTrapAddress ( trapWord & 0x07FF );
- else
- return GetOSTrapAddress ( trapWord & 0x07FF );
- }
-
-
- If the machine crashes after leaving your patch you have probably munged
- the stack or not saved and restored all the registers that you must.
-
- The Finder patches a number of traps when it loads in a way that prevents
- earlier trap patches from functioning. If your patch doesn't appear to be
- called it may be one of these patches.
-
- --
- [26] What other sources of information are available?
-
- Knaster 'How to Write Macintosh Software'
- Knaster and Rollin, 'Macintosh Programming Secrets'. These books on Mac
- programming has some excellent info on trap patching.
-
- Tom Thomson 'Power Macintosh Programming Starter Kit' This book has some
- example code for writing trap patches and extensions on the PowerMac.
-
- The Extension Shell package (by Dair Grant, dair@kagi.com) at:
- ftp://sumex-aim.stanford.edu/info-mac/dev/src/extension-shell-15.hqx
-
- Usenet Macintosh Programmers Guide
- ftp://sumex-aim.stanford.edu/info-mac/dev/info/usenet-mac-prog-guide-msw.hqx
-
- All of the Apple Tech Notes have been made available on Apple's web server:
- http://www.info.apple.com/dev/technotes/Main.html
-